/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *   gbx-emu - screen.cpp                                                  *
 *   gbx-emu homepage: http://code.google.com/p/gbx-emu/                   *
 *   Copyright (C) 2009 olejl77@gmail.com                                  *
 *                                                                         *
 *   This program is free software: you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation, either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the          *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "screen.h"
#include <QImage>
#include <QRgb>
#include "gameboy.h"
#include "memory.h"
#include "cpu.h"

Screen::Screen(Gameboy *ptr)
{
    _gb = ptr;
    _image = NULL;
    _mem = NULL;
}

Screen::~Screen()
{
    _gb = NULL;
    _image = NULL;
    _mem = NULL;
}

void Screen::setImage(QImage *ptr)
{
    _image = ptr;
    QRgb value;
    value = qRgb(0xff, 0xff, 0xff);
    _image->setColor(0, value);
    value = qRgb(0xcc, 0xcc, 0xcc);
    _image->setColor(1, value);
    value = qRgb(0x77, 0x77, 0x77);
    _image->setColor(2, value);
    value = qRgb(0x00, 0x00, 0x00);
    _image->setColor(3, value);
    resetImage();
}

void Screen::resetImage()
{
    for (s32 y = 0 ; y < 144; y++) {
        for (s32 x = 0; x < 160; x++) {
            _image->setPixel(x,y,0);
        }
    }
}

void Screen::DrawLine(u8 lcdc, u8 ly, u8 wy, u8 wx, u8 scy, u8 scx)
{
//    bool bg_display_enable =  (check_bit(lcdc, 0)==1) ? true : false;
    bool sprite_display_enable =(check_bit(lcdc, 1)==1) ? true : false;
    bool sprite_size_8x16 =     (check_bit(lcdc, 2)==1) ? true : false;
    u16 background_tile_map =   (check_bit(lcdc, 3)==1) ? 0x9800 : 0x9c00;
    u16 tile_data_table =       (check_bit(lcdc, 4)==1) ? 0x8000 : 0x8800;
    bool window_display_enable =(check_bit(lcdc, 5)==1) ? true : false;
//    bool LCD_display_enable = (check_bit(lcdc, 7)==1) ? true : false;

    u32 y_pixel, x_pixel, tile_row, tile_col;

    if (window_display_enable) {
        if (wy > ly) {
            window_display_enable = false;
        }
    }
    if (window_display_enable) {
        if (check_bit(lcdc, 6)) {
            background_tile_map = 0x9c00;
        } else {
            background_tile_map = 0x9800;
        }
        y_pixel = ly - wy;
    } else {
        if (check_bit(lcdc, 3)) {
            background_tile_map = 0x9c00;
        } else {
            background_tile_map = 0x9800;
        }
        y_pixel = scy + ly;
    }
    tile_row = (y_pixel / 8) * 32;

    for (u32 pixel = 0; pixel < 160; pixel++) {
        x_pixel = pixel + scx;
        if (window_display_enable) {
            if (pixel >= wx) {
                x_pixel = pixel - wx;
            }
        }

        tile_col = (x_pixel / 8);

        s32 tile_no;
        if (tile_data_table == 0x8000) {
            tile_no = (u8) _mem->read(background_tile_map + tile_row + tile_col);
        } else {
            s8 s = _mem->read(background_tile_map + tile_row + tile_col);
            tile_no = s;
            tile_no += 128;
        }

        u16 tile_addr = tile_data_table + (tile_no * 16);

        u32 line = (y_pixel % 8) * 2;
        u16 addr = tile_addr + line;
        u32 byte1 = _mem->read(addr);
        u32 byte2 = _mem->read(addr + 1);

        s32 color_bit = x_pixel % 8 ;
        color_bit -= 7 ;
        color_bit *= -1 ;

        u32 color = check_bit(byte2, color_bit) ? 1 : 0;
        color <<= 1;
        color |= check_bit(byte1, color_bit) ? 1 : 0;

        _image->setPixel(pixel, ly, color);
    }
    if (sprite_display_enable) {
        for (u32 sprite = 0; sprite < 40; sprite++) {
            u32 index = sprite * 4;
            y_pixel = _mem->read(0xfe00 + index);
            x_pixel = _mem->read(0xfe00 + index + 1);

            // An offscreen value (for example, Y=0 or Y>=160) hides the sprite.
            if ((y_pixel == 0) || (y_pixel >= 160)) {
                continue;
            }

            // An offscreen value (X=0 or X>=168) hides the sprite, but the sprite
            // still affects the priority ordering - a better way to hide a sprite is to set its Y-coordinate offscreen.
            if ((x_pixel == 0) || (x_pixel >= 168)) {
                // TODO: priority ordering??
                continue;
            }
            y_pixel -= 16;
            x_pixel -= 8;
            u32 tile_no = _mem->read(0xfe00 + index + 2);
            u32 tile_attr = _mem->read(0xfe00 + index + 3);

            // Bit7 OBJ-to-BG Priority (0=OBJ Above BG, 1=OBJ Behind BG color 1-3) (Used for both BG and Window. BG color 0 is always behind OBJ)
            u32 y_flip = check_bit(tile_attr, 6) ? 1 : 0;
            u32 x_flip = check_bit(tile_attr, 5) ? 1 : 0;
            // Bit4   Palette number  **Non CGB Mode Only** (0=OBP0, 1=OBP1)
            // Bit3   Tile VRAM-Bank  **CGB Mode Only**     (0=Bank 0, 1=Bank 1)
            // Bit2-0 Palette number  **CGB Mode Only**     (OBP0-7)

            u32 y_size = sprite_size_8x16 ? 16 : 8;

            // Check if the sprite has any pixels in the current scanline
            if ((ly >= y_pixel) && (ly < (y_pixel + y_size))) {
//                _log->Msg(DEBUG_LEVEL, LOG_SCREEN, "Sprite, tile no.: %.2x", tile_no);
                s32 line = ly - y_pixel;

                if (y_flip) {
                    line -= y_size;
                    line *= -1;
                }
                line *= 2;
                u32 byte1 = _mem->read((0x8000 + (tile_no * 16)) + line);
                u32 byte2 = _mem->read((0x8000 + (tile_no * 16)) + line + 1);

//                for (s32 tilePixel = 7; tilePixel >= 0; tilePixel--) {
                for (u32 pixel = 0; pixel < 8; pixel++) {
                    s32 color_bit = pixel;
                    if (!x_flip) {
                        color_bit -= 7;
                        color_bit *= -1;
                    }
                    u32 color = check_bit(byte2, color_bit) ? 1 : 0;
                    color <<= 1;
                    color |= check_bit(byte1, color_bit) ? 1 : 0;

                    // white is transparent for sprites.
                    if (color == 0)
                        continue;

                    // TODO: Unsecure about this one ...
                    s32 pix = (x_pixel + pixel) % 160;

                    // Bit7   OBJ-to-BG Priority (0=OBJ Above BG, 1=OBJ Behind BG color 1-3)
                    // (Used for both BG and Window. BG color 0 is always behind OBJ)
                    if (!check_bit(tile_attr, 7)) {
                        // 0=OBJ Above BG
                        _image->setPixel(pix, ly, color);
                    } else {
                        QRgb value;
                        value = qRgb(0xff, 0xff, 0xff);
                        if (_image->pixel(pix, ly) == value) {
                            _image->setPixel(pix, ly, color);
                        }
                    }
                }
            }
        }
    }
}

